/* wintapi.c */

/********************/
/* TAPI-INTERFACE   */
/* for MS-Windows   */
/* (C) 2003 by NAH6 */
/********************/

/*------------*/
/*  INCLUDES  */
/*------------*/

#include <memory.h>
#include <windows.h>
#include <tapi.h>

#include "wintapi.h"
#include "wintop.h"

/*-----------*/
/*  DEFINES  */
/*-----------*/

#define K_TAPI_DISCMODES   (16)

/*--------------*/
/*  PROTOTYPES  */
/*--------------*/

static signed short  tapi__startup  (DWORD *dllapi, HLINEAPP *lapp, \
                                     HANDLE *tevt, DWORD *lines);
static signed short  tapi__getlcaps (DWORD *tspapi, LINEDEVCAPS **lcaps, \
                                     HLINEAPP lapp, unsigned char line);
static signed short  tapi__getacaps (LINEADDRESSCAPS **acaps, DWORD tspapi, \
                                     HLINEAPP lapp, unsigned char line,     \
                                     unsigned char adr);
static unsigned char tapi__chklcaps (DWORD api, const LINEDEVCAPS *caps, \
                                     unsigned char gsm);
static unsigned char tapi__chkacaps (DWORD api, const LINEADDRESSCAPS *caps);

static signed short  tapi__getmsg   (struct tapi_data *dseg, \
                                     unsigned char nobrk);
static signed short  tapi__mkcinfos (struct tapi_data *dseg);
static signed short  tapi__ci2link  (struct tapi_data *dseg, \
                                     struct tapi_link *link);
static signed short  tapi__discerr  (const struct tapi_data *dseg);

/*-------------*/
/*  VARIABLEN  */
/*-------------*/

static const TCHAR tapi__appname[]     = TEXT (K_TAPI_APPNAME);
static const TCHAR tapi__class_modem[] = TEXT ("comm/datamodem");
static const TCHAR tapi__class_audio[] = TEXT ("wave/in/out");

static const DWORD tapi__discmode[K_TAPI_DISCMODES] =
{
  LINEDISCONNECTMODE_NORMAL,        LINEDISCONNECTMODE_PICKUP,      \
  LINEDISCONNECTMODE_FORWARDED,     LINEDISCONNECTMODE_NODIALTONE,  \
  LINEDISCONNECTMODE_OUTOFORDER,    LINEDISCONNECTMODE_TEMPFAILURE, \
  LINEDISCONNECTMODE_CONGESTION,    LINEDISCONNECTMODE_BADADDRESS,  \
  LINEDISCONNECTMODE_NUMBERCHANGED, LINEDISCONNECTMODE_UNREACHABLE, \
  LINEDISCONNECTMODE_INCOMPATIBLE,  LINEDISCONNECTMODE_NOANSWER,    \
  LINEDISCONNECTMODE_BUSY,          LINEDISCONNECTMODE_BLOCKED,     \
  LINEDISCONNECTMODE_REJECT,        LINEDISCONNECTMODE_DONOTDISTURB
};

/*############################################################################*/

/*---------------*/
/*  TAPI-DETECT  */
/*---------------*/
                                                                    /* [5201] */
void tapi_hwdetect_create (struct tapi_hwinfo *info)  /* Create Hardware-Info */
{
  unsigned char x,y;
  signed short n,m;
  HLINEAPP happ;
  HANDLE tevt;
  DWORD lines;

  /* Tapi-detect */
  n = tapi__startup (&(info->dllapi), &happ, &tevt, &lines);
  for (x=0; x<K_TAPI_MAXLINES; x++)
  {
    /* Tapi-Devices */
    if (n >= 0)
    {
      if (x < lines)
      {
        m = tapi__getlcaps (&(info->line[x].tspapi), &(info->line[x].caps), \
                            happ, x);
        if (m < 0)
          goto err_exit;
        info->line[x].valid = K_TAPI_DEVICE_OK;
      }
      else
        info->line[x].valid = K_TAPI_DEVICE_NOHW;
    }
    else
    {
      err_exit:
      info->line[x].valid = K_TAPI_DEVICE_ERROR;
    }

    /* Device-Addresses */
    for (y=0; y<K_TAPI_MAXADR; y++)
    {
      if (info->line[x].valid != K_TAPI_DEVICE_OK)
        info->adr[x][y].valid = info->line[x].valid;
      else
      {
        if (y < info->line[x].caps->dwNumAddresses)
        {
          m = tapi__getacaps (&(info->adr[x][y].caps), info->line[x].tspapi, \
                              happ, x, y);
          if (m < 0)
          {
            info->adr[x][y].valid = K_TAPI_DEVICE_ERROR;
            continue;
          }
          info->adr[x][y].valid = K_TAPI_DEVICE_OK;
        }
        else
          info->adr[x][y].valid = K_TAPI_DEVICE_NOHW;
      }
    }
  }
  if (n >= 0)
    lineShutdown (happ);
}

/*----*/                                                            /* [5202] */
                                                     /* Destroy Hardware-Info */
void tapi_hwdetect_destroy (const struct tapi_hwinfo *info)
{
  unsigned char x,y;
  HANDLE heap;

  /* Free memory */
  heap = GetProcessHeap ();
  for (x=0; x<K_TAPI_MAXLINES; x++)
  {
    if (info->line[x].valid == K_TAPI_DEVICE_OK)
    {
      HeapFree (heap, 0L, info->line[x].caps);
      for (y=0; y<K_TAPI_MAXADR; y++)
      {
        if (info->adr[x][y].valid == K_TAPI_DEVICE_OK)
          HeapFree (heap, 0L, info->adr[x][y].caps);
        else if (info->adr[x][y].valid == K_TAPI_DEVICE_NOHW)
          break;
      }
    }
    else if (info->line[x].valid == K_TAPI_DEVICE_NOHW)
      break;
  }
}

/*----------------*/
/*  TAPI-AUTOCFG  */
/*----------------*/                                                /* [5203] */
                                          /* Auto-Select Tapi-Hardware-Config */
void tapi_hwautocfg (struct tapi_config *config, const struct tapi_hwinfo *info)
{
  unsigned char n,x,y;
  signed char gsm;

  /* Find device/address */
  config->sigterm = NULL;
  for (gsm=1; gsm>=0; gsm--)
    for (x=0; x<K_TAPI_MAXLINES; x++)
    {
      if (info->line[x].valid == K_TAPI_DEVICE_OK)
      {
        n = tapi__chklcaps (info->line[x].tspapi, info->line[x].caps, gsm);
        if (n)
          for (y=0; y<K_TAPI_MAXADR; y++)
          {
            if (info->adr[x][y].valid == K_TAPI_DEVICE_OK)
            {
              n = tapi__chkacaps (info->line[x].tspapi, info->adr[x][y].caps);
              if (n)
              {
                config->line = x;
                config->adr  = y;
                return;
              }
            }
            else if (info->adr[x][y].valid == K_TAPI_DEVICE_NOHW)
              break;
          }
      }
      else if (info->line[x].valid == K_TAPI_DEVICE_NOHW)
        break;
    }
  config->line = 0;
  config->adr  = 0;
}

/*====*/
                                                             /* Init Tapi-DLL */
static signed short tapi__startup (DWORD *dllapi, HLINEAPP *happ, \
                                   HANDLE *tevt, DWORD *lines)
{
  LINEINITIALIZEEXPARAMS lpar;
  LONG rc;

  /* Init Tapi-DLL */
  *dllapi = K_TAPI_MAXVER;
  lpar.dwTotalSize     = sizeof (LINEINITIALIZEEXPARAMS);
  lpar.dwNeededSize    = 0L;
  lpar.dwUsedSize      = 0L;
  lpar.dwOptions       = LINEINITIALIZEEXOPTION_USEEVENT;
  lpar.Handles.hEvent  = 0;
  lpar.dwCompletionKey = 0L;
  rc = lineInitializeEx (happ, wintop_inst, NULL, tapi__appname, lines, \
                         dllapi, &lpar);
  if (rc)
    return E_TAPI_DLLINIT;
  if (*dllapi < K_TAPI_MINDLLVER)
  {
    lineShutdown (*happ);
    return E_TAPI_DLLVER;
  }
  *tevt = lpar.Handles.hEvent;
  return 0;
}

/*----*/
                                                            /* Read Line-Caps */
static signed short tapi__getlcaps (DWORD *tspapi, LINEDEVCAPS **lcaps, \
                                    HLINEAPP happ, unsigned char line)
{
#ifndef _WIN32_WCE
  LINEEXTENSIONID lxid;
#endif
  LINEDEVCAPS *caps;
  signed short n;
  HANDLE heap;
  DWORD d,e,f;
  LONG rc;

  /* Negotiate TSP-version */
#ifndef _WIN32_WCE
  rc = lineNegotiateAPIVersion (happ, line, K_TAPI_MINTSPVER, K_TAPI_MAXVER, \
       tspapi, &lxid);
#else
  rc = lineNegotiateAPIVersion (happ, line, K_TAPI_MINTSPVER, K_TAPI_MAXVER, \
       tspapi, NULL);
#endif
  if (rc == LINEERR_INCOMPATIBLEAPIVERSION)
    return E_TAPI_TSPVER;
  if (rc)
    return E_TAPI_TSPINIT;

  /* Read line-capabilities */
  heap = GetProcessHeap ();
  caps = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (caps == NULL)
    return E_TAPI_NOMEM;
  d = HeapSize (heap, 0L, caps);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit1;
  caps->dwTotalSize  = d;
  caps->dwNeededSize = 0L;
  caps->dwUsedSize   = 0L;
  rc = lineGetDevCaps (happ, line, *tspapi, 0L, caps);
  if (rc)
    goto err_exit2;
  e = caps->dwNeededSize;
  if (caps->dwUsedSize > e)
    e = caps->dwUsedSize;
  if (e < sizeof (LINEDEVCAPS))
    goto err_exit2;
  if (e == d)
    *lcaps = caps;
  else
  {
    *lcaps = HeapReAlloc (heap, 0L, caps, e);
    if (*lcaps == NULL)
      goto err_exit1;
    caps = *lcaps;
    f = HeapSize (heap, 0L, caps);
    if (f < e)
    {
      err_exit1:
      n = E_TAPI_NOMEM;
      goto err_exit3;
    }
    caps->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetDevCaps (happ, line, *tspapi, 0L, caps);
      if (rc)
      {
        err_exit2:
        n = E_TAPI_LINECAPS;
        err_exit3:
        HeapFree (heap, 0L, caps);
        return n;
      }
    }
  }
  return 0;
}

                                                         /* Read Address-Caps */
static signed short tapi__getacaps (LINEADDRESSCAPS **acaps, DWORD tspapi, \
                                    HLINEAPP happ, unsigned char line,     \
                                    unsigned char adr)
{
  LINEADDRESSCAPS *caps;
  signed short n;
  HANDLE heap;
  DWORD d,e,f;
  LONG rc;

  /* Read address-capabilities */
  heap = GetProcessHeap ();
  caps = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (caps == NULL)
    return E_TAPI_NOMEM;
  d = HeapSize (heap, 0L, caps);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit1;
  caps->dwTotalSize  = d;
  caps->dwNeededSize = 0L;
  caps->dwUsedSize   = 0L;
  rc = lineGetAddressCaps (happ, line, adr, tspapi, 0L, caps);
  if (rc)
    goto err_exit2;
  e = caps->dwNeededSize;
  if (caps->dwUsedSize > e)
    e = caps->dwUsedSize;
  if (e < sizeof (LINEADDRESSCAPS))
    goto err_exit2;
  if (e == d)
    *acaps = caps;
  else
  {
    *acaps = HeapReAlloc (heap, 0L, caps, e);
    if (*acaps == NULL)
      goto err_exit1;
    caps = *acaps;
    f = HeapSize (heap, 0L, caps);
    if (f < e)
    {
      err_exit1:
      n = E_TAPI_NOMEM;
      goto err_exit3;
    }
    caps->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetAddressCaps (happ, line, adr, tspapi, 0L, caps);
      if (rc)
      {
        err_exit2:
        n = E_TAPI_ADRCAPS;
        err_exit3:
        HeapFree (heap, 0L, caps);
        return n;
      }
    }
  }
  return 0;
}

/*----*/
                                                           /* Check Tapi-Line */
static unsigned char tapi__chklcaps (DWORD api, const LINEDEVCAPS *caps, \
                                     unsigned char gsm)
{
  /* Check line-capabilities */
  if ((caps->dwNumAddresses <= 0)                                \
   || (!(caps->dwBearerModes                                     \
       & (LINEBEARERMODE_DATA | LINEBEARERMODE_PASSTHROUGH)))    \
   || (!(caps->dwMediaModes                                      \
       & (LINEMEDIAMODE_DATAMODEM | LINEMEDIAMODE_DIGITALDATA))) \
   || (gsm && (caps->dwMaxRate != 9600L)))
    return 0;
#ifdef _WIN32_WCE
  if (api >= 0x00010004L)
    if (!(caps->dwLineFeatures & LINEFEATURE_MAKECALL))
      return 0;
#endif
  return 1;
}

                                                        /* Check Line-Address */
static unsigned char tapi__chkacaps (DWORD api, const LINEADDRESSCAPS *caps)
{
  DWORD d;

  /* Check address-capabilities */
  d = LINECALLFEATURE_ANSWER | LINECALLFEATURE_DIAL | LINECALLFEATURE_DROP;
  if ((caps->dwCallFeatures & d) != d)
    return 0;
#ifndef _WIN32_WCE
  if (api >= 0x00010004L)
  {
    if (!(caps->dwAddressFeatures & LINEADDRFEATURE_MAKECALL))
      return 0;
    if (api >= 0x00020000L)
      if (caps->dwAvailableMediaModes && !(caps->dwAvailableMediaModes \
          & (LINEMEDIAMODE_DATAMODEM | LINEMEDIAMODE_DIGITALDATA)))
        return 0;
  }
#endif
  return 1;
}

/*----------------------------------------------------------------------------*/

/*--------------*/
/*  OPEN/CLOSE  */
/*--------------*/                                                  /* [5204] */
                                                          /* Open Tapi-Driver */
signed short tapi_open (struct tapi_data *dseg, const struct tapi_config *cfg)
{
  LINEADDRESSSTATUS *astat;
  LINEDEVSTATUS *lstat;
  unsigned char x;
  signed short n;
  HANDLE heap;
  DWORD d,e,f;
  LONG rc;

  /* Check pointer */
  if ((dseg == NULL) || (cfg == NULL))
    return E_TAPI_NULLPTR;

  /* Data init */
  memcpy (&(dseg->cfg), cfg, sizeof (struct tapi_config));
  dseg->callstate = K_TAPI_CALLSTATE_IDLE;
  dseg->jobid     = 0xFFFFFFFFL;
  heap = GetProcessHeap ();

  /* DLL init */
  n = tapi__startup (&(dseg->dllapi), &(dseg->happ), &(dseg->tevt), &d);
  if (n < 0)
    return n;
  if (dseg->cfg.line >= d)
  {
    n = E_TAPI_LINENUM;
    goto err_exit10;
  }

  /* Line init */
  n = tapi__getlcaps (&(dseg->tspapi), &(dseg->lcaps), dseg->happ, \
                      dseg->cfg.line);
  if (n < 0)
  {
    n = E_TAPI_LINECAPS;
    goto err_exit10;
  }
  x = tapi__chklcaps (dseg->tspapi, dseg->lcaps, 0);
  if (!x)
  {
    n = E_TAPI_LINECAPS;
    goto err_exit9;
  }
  if (dseg->cfg.adr >= dseg->lcaps->dwNumAddresses)
  {
    n = E_TAPI_ADRNUM;
    goto err_exit9;
  }

  /* Address init */
  n = tapi__getacaps (&(dseg->acaps), dseg->tspapi, dseg->happ, \
                      dseg->cfg.line, dseg->cfg.adr);
  if (n < 0)
  {
    n = E_TAPI_ADRCAPS;
    goto err_exit9;
  }
  x = tapi__chkacaps (dseg->tspapi, dseg->acaps);
  if (!x)
  {
    n = E_TAPI_ADRCAPS;
    goto err_exit8;
  }

  /* Line open */
  d = dseg->lcaps->dwMediaModes & (LINEMEDIAMODE_DATAMODEM \
    | LINEMEDIAMODE_DIGITALDATA);
  rc = lineOpen (dseg->happ, dseg->cfg.line, &(dseg->hline), dseg->tspapi, 0L, \
                 0L, LINECALLPRIVILEGE_OWNER, d, NULL);
  if (rc)
  {
    n = E_TAPI_LINEOPEN;
    goto err_exit8;
  }

  /* Line status */
  lstat = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (lstat == NULL)
  {
    n = E_TAPI_NOMEM;
    goto err_exit7;
  }
  d = HeapSize (heap, 0L, lstat);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit1;
  lstat->dwTotalSize  = d;
  lstat->dwNeededSize = 0L;
  lstat->dwUsedSize   = 0L;
  rc = lineGetLineDevStatus (dseg->hline, lstat);
  if (rc)
    goto err_exit2;
  e = lstat->dwNeededSize;
  if (lstat->dwUsedSize > e)
    e = lstat->dwUsedSize;
  if (e < sizeof (LINEDEVSTATUS))
    goto err_exit2;
  if (e == d)
    dseg->lstat = lstat;
  else
  {
    dseg->lstat = HeapReAlloc (heap, 0L, lstat, e);
    if (dseg->lstat == NULL)
      goto err_exit1;
    lstat = dseg->lstat;
    f = HeapSize (heap, 0L, lstat);
    if (f < e)
    {
      err_exit1:
      n = E_TAPI_NOMEM;
      goto err_exit6;
    }
    lstat->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetLineDevStatus (dseg->hline, lstat);
      if (rc)
      {
        err_exit2:
        n = E_TAPI_LINESTAT;
        goto err_exit6;
      }
    }
  }

  /* Address status */
  astat = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (astat == NULL)
  {
    n = E_TAPI_NOMEM;
    goto err_exit6;
  }
  d = HeapSize (heap, 0L, astat);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit3;
  astat->dwTotalSize  = d;
  astat->dwNeededSize = 0L;
  astat->dwUsedSize   = 0L;
  rc = lineGetAddressStatus (dseg->hline, dseg->cfg.adr, astat);
  if (rc)
    goto err_exit4;
  e = astat->dwNeededSize;
  if (astat->dwUsedSize > e)
    e = astat->dwUsedSize;
  if (e < sizeof (LINEADDRESSSTATUS))
    goto err_exit4;
  if (e == d)
    dseg->astat = astat;
  else
  {
    dseg->astat = HeapReAlloc (heap, 0L, astat, e);
    if (dseg->astat == NULL)
      goto err_exit3;
    astat = dseg->astat;
    f = HeapSize (heap, 0L, astat);
    if (f < e)
    {
      err_exit3:
      n = E_TAPI_NOMEM;
      goto err_exit5;
    }
    astat->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetAddressStatus (dseg->hline, dseg->cfg.adr, astat);
      if (rc)
      {
        err_exit4:
        n = E_TAPI_ADRSTAT;
        goto err_exit5;
      }
    }
  }

  /* Message mask */
  rc = lineSetStatusMessages (dseg->hline, dseg->lcaps->dwLineStates, \
                              dseg->acaps->dwAddressStates);
  if (rc)
  {
    n = E_TAPI_MSGMASK;
    err_exit5:
    HeapFree (heap, 0L, astat);
    err_exit6:
    HeapFree (heap, 0L, lstat);
    err_exit7:
    lineClose (dseg->hline);
    err_exit8:
    HeapFree (heap, 0L, dseg->acaps);
    err_exit9:
    HeapFree (heap, 0L, dseg->lcaps);
    err_exit10:
    lineShutdown (dseg->happ);
    return n;
  }
  return 0;
}

/*----*/
                                                                    /* [5205] */
signed short tapi_close (struct tapi_data *dseg)         /* Close Tapi-Driver */
{
  signed short n,m;
  HANDLE heap;
  LONG rc;

  /* Ptr-Check, Hangup */
  n = tapi_hangup (dseg);
  if (n == E_TAPI_NULLPTR)
    return n;

  /* Remove Zombies */
  do
  {
    m = tapi__getmsg (dseg, 1);
  } while (m > K_TAPI_MSG_NONE);
  if ((m < 0) && (n >= 0))
    n = m;

  /* Close Tapi */
  heap = GetProcessHeap ();
  HeapFree (heap, 0L, dseg->astat);
  HeapFree (heap, 0L, dseg->lstat);
  rc = lineClose (dseg->hline);
  if (rc && (n >= 0))
    n = E_TAPI_LINECLOSE;
  HeapFree (heap, 0L, dseg->acaps);
  HeapFree (heap, 0L, dseg->lcaps);
  rc = lineShutdown (dseg->happ);
  if (rc && (n >= 0))
    n = E_TAPI_LINESHUTDOWN;
  return n;
}

/*-----------*/
/*  MONITOR  */
/*-----------*/                                                     /* [5210] */
                                                               /* Watch Modem */
signed short tapi_watch (struct tapi_data *dseg, struct tapi_link *link)
{
  signed short n,m;
  LONG rc;

  /* Check Pointer */
  if (dseg == NULL)
    return E_TAPI_NULLPTR;

  /* Read Messages */
  while (42)
  {
    n = tapi__getmsg (dseg, 0);
    if (n < 0)
      goto err_exit;
    else if (n == K_TAPI_WINMSG_NONE)
    {
      n = K_TAPI_MSG_NONE;
      break;
    }
    else if (n == K_TAPI_WINMSG_OFFER)
    {
      if (dseg->acaps->dwCallFeatures & LINECALLFEATURE_ACCEPT)
      {
        rc = lineAccept (dseg->hcall, NULL, 0L);
        if (rc < 0)
        {
          n = E_TAPI_ACCEPT1;
          goto err_exit;
        }
        dseg->jobid = rc;
        do
        {
          n = tapi__getmsg (dseg, 0);
          if (n < 0)
            goto err_exit;
        } while (n != K_TAPI_WINMSG_REPLY);
        if (dseg->result)
        {
          n = E_TAPI_ACCEPT2;
          goto err_exit;
        }
      }
      n = tapi__ci2link (dseg, link);
      if (n < 0)
        goto err_exit;
      n = K_TAPI_MSG_RINGON;
      break;
    }
    else if ((n == K_TAPI_WINMSG_LINEDEVSTATE) \
          && (dseg->mlstat & LINEDEVSTATE_RINGING)
          && (dseg->callstate == K_TAPI_CALLSTATE_RINGING))
    {
      n = tapi__ci2link (dseg, link);
      if (n < 0)
        goto err_exit;
      n = K_TAPI_MSG_RINGING;
      break;
    }
    else if ((n == K_TAPI_WINMSG_CALLSTATE) \
          && ((dseg->mcstat == LINECALLSTATE_DISCONNECTED) \
           || (dseg->mcstat == LINECALLSTATE_IDLE)))
    {
      if (dseg->callstate != K_TAPI_CALLSTATE_RINGING)
      {
        n = tapi__discerr (dseg);
        m = tapi_hangup (dseg);
        if (m < 0)
          n = m;
      }
      else
      {
        n = K_TAPI_MSG_RINGOFF;
        err_exit:
        m = tapi_hangup (dseg);
        if ((m < 0) && (n >= 0))
          n = m;
      }
      break;
    }
  }
  return n;
}

/*-----------*/
/*  CONNECT  */
/*-----------*/                                                     /* [5211] */
                                                              /* Connect Call */
signed short tapi_connect (struct tapi_data *dseg, struct tapi_link *link)
{
#ifdef _UNICODE
  TCHAR cnum[K_TAPI_DIALNUMBUFLEN];
#endif
  LINECALLPARAMS cpar;
  VARSTRING *vstr;
  unsigned char t;
  signed short n;
  const TCHAR *p;
  const DWORD *q;
  HANDLE heap;
  DWORD d;
  LONG rc;

  /* Check Pointer */
  if ((dseg == NULL) || (link == NULL))
    return E_TAPI_NULLPTR;

  /* Dialin */
  if (!(link->dialout))
  {
    /* Verify ringing */
    if (dseg->callstate != K_TAPI_CALLSTATE_RINGING)
    {
      n = E_TAPI_NORING;
      goto err_exit3;
    }

    /* Answer call */
    rc = lineAnswer (dseg->hcall, NULL, 0);
    if (rc < 0)
    {
      n = E_TAPI_ANSWER1;
      goto err_exit3;
    }
    dseg->jobid = rc;
    do
    {
      n = tapi__getmsg (dseg, 0);
      if (n < 0)
        goto err_exit3;
    } while (n != K_TAPI_WINMSG_REPLY);
    if (dseg->result)
    {
      n = E_TAPI_ANSWER2;
      goto err_exit3;
    }
    t = K_TAPI_DIALINTIMEOUT;
  }

  /* Dialout */
  else
  {
    /* Hangup */
    n = tapi_hangup (dseg);
    if (n < 0)
      return n;

    /* Check parameter */
    if (link->remnumlen > K_TAPI_DIALNUMBUFLEN-1)
      link->remnumlen = K_TAPI_DIALNUMBUFLEN-1;
    link->remnumbuf[link->remnumlen] = 0;
    if (link->service == K_TAPI_LINKSERVICE_UNKNOWN)
      link->service = K_TAPI_LINKSERVICE_DATA;
    if (link->carrier == K_TAPI_LINKCARRIER_UNKNOWN)
      link->carrier = K_TAPI_LINKCARRIER_DIG;
    if (link->service >= K_TAPI_LINKSERVICES)
      return E_TAPI_LINKSERV;
    if (link->carrier >= K_TAPI_LINKCARRIERS)
      return E_TAPI_LINKCARR;

    /* Build call-parameter */
#ifdef _UNICODE
    for (n=0; n<K_TAPI_DIALNUMBUFLEN; n++)
    {
      cnum[n] = link->remnumbuf[n];
      if (!(link->remnumbuf[n]))
        break;
    }
#endif
    memset (&cpar, 0x00, sizeof (LINECALLPARAMS));
    cpar.dwTotalSize = sizeof (LINECALLPARAMS);
    if (link->service == K_TAPI_LINKSERVICE_VOICE)
    {
      if (dseg->lcaps->dwBearerModes & LINEBEARERMODE_SPEECH)
        cpar.dwBearerMode = LINEBEARERMODE_SPEECH;
      else if (dseg->lcaps->dwBearerModes & LINEBEARERMODE_VOICE)
        cpar.dwBearerMode = LINEBEARERMODE_VOICE;
      else
        return E_TAPI_LINKSERV;
      if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_AUTOMATEDVOICE)
        cpar.dwMediaMode = LINEMEDIAMODE_AUTOMATEDVOICE;
      else if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE)
        cpar.dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
      else
        return E_TAPI_LINKSERV;
    }
    else
    {
      if ((link->carrier == K_TAPI_LINKCARRIER_ANA) \
       && (dseg->lcaps->dwBearerModes & LINEBEARERMODE_VOICE))
        cpar.dwBearerMode = LINEBEARERMODE_VOICE;
      else if (dseg->lcaps->dwBearerModes & LINEBEARERMODE_DATA)
        cpar.dwBearerMode = LINEBEARERMODE_DATA;
      else if (dseg->lcaps->dwBearerModes & LINEBEARERMODE_PASSTHROUGH)
        cpar.dwBearerMode = LINEBEARERMODE_PASSTHROUGH;
      else if (dseg->lcaps->dwBearerModes & LINEBEARERMODE_VOICE)
        cpar.dwBearerMode = LINEBEARERMODE_VOICE;
      else
        return E_TAPI_LINKSERV;
      if (link->service == K_TAPI_LINKSERVICE_FAX)
      {
        if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_G4FAX)
          cpar.dwMediaMode = LINEMEDIAMODE_G4FAX;
        else if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_G3FAX)
          cpar.dwMediaMode = LINEMEDIAMODE_G3FAX;
        else
          return E_TAPI_LINKSERV;
      }
      else
      {
        if ((link->carrier == K_TAPI_LINKCARRIER_ANA) \
         && (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM))
          cpar.dwMediaMode = LINEMEDIAMODE_DATAMODEM;
        else if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_DIGITALDATA)
          cpar.dwMediaMode = LINEMEDIAMODE_DIGITALDATA;
        else if (dseg->lcaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM)
          cpar.dwMediaMode = LINEMEDIAMODE_DATAMODEM;
        else
          return E_TAPI_LINKSERV;
      }
    }
    cpar.dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;
    cpar.dwAddressMode    = LINEADDRESSMODE_ADDRESSID;
    cpar.dwAddressID      = dseg->cfg.adr;

    /* Create call */
#ifdef _UNICODE
    rc = lineMakeCall (dseg->hline, &(dseg->hcall), cnum, 0, &cpar);
#else
    rc = lineMakeCall (dseg->hline, &(dseg->hcall), link->remnumbuf, 0, &cpar);
#endif
    if (rc < 0)
      return E_TAPI_MAKECALL1;
    dseg->jobid = rc;
    do
    {
      n = tapi__getmsg (dseg, 1);
      if (n < 0)
        return n;
    } while (n != K_TAPI_WINMSG_REPLY);
    if (dseg->result)
      return E_TAPI_MAKECALL2;
    n = tapi__mkcinfos (dseg);
    if (n < 0)
    {
      lineDrop (dseg->hcall, NULL, 0);
      return n;
    }
    t = K_TAPI_DIALOUTTIMEOUT;
  }

  /* Connect */
  dseg->callstate = K_TAPI_CALLSTATE_CONNECTING;
  while ((dseg->mcstat != LINECALLSTATE_CONNECTED)    \
      && (dseg->mcstat != LINECALLSTATE_DISCONNECTED) \
      && (dseg->mcstat != LINECALLSTATE_IDLE))
  {
    n = tapi__getmsg (dseg, 0);
    if (n < 0)
      goto err_exit3;
    else if (n == K_TAPI_WINMSG_NONE)
    {
      t--;
      if (!t)
      {
        n = E_TAPI_CALL_TIMEOUT;
        goto err_exit3;
      }
    }
  }
  if ((dseg->mcstat == LINECALLSTATE_DISCONNECTED) \
   || (dseg->mcstat == LINECALLSTATE_IDLE))
  {
    n = tapi__discerr (dseg);
    goto err_exit3;
  }
  n = tapi__ci2link (dseg, link);
  if (n < 0)
    goto err_exit3;
  dseg->service = link->service;

  /* Get I/O-device */
  if (link->service != K_TAPI_LINKSERVICE_UNKNOWN)
  {
    heap = GetProcessHeap ();
    vstr = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
    if (vstr == NULL)
    {
      n = E_TAPI_NOMEM;
      goto err_exit3;
    }
    d = HeapSize (heap, 0L, vstr);
    if (d < K_TAPI_PARBLKLEN)
    {
      n = E_TAPI_NOMEM;
      goto err_exit2;
    }
    vstr->dwTotalSize  = d;
    vstr->dwNeededSize = 0L;
    vstr->dwUsedSize   = 0L;
    if (link->service == K_TAPI_LINKSERVICE_VOICE)
      p = tapi__class_audio;
    else
      p = tapi__class_modem;
    rc = lineGetID (dseg->hline, dseg->cfg.adr, dseg->hcall, \
                    LINECALLSELECT_CALL, vstr, p);
    if (rc)
      goto err_exit1;
    if (link->service == K_TAPI_LINKSERVICE_VOICE)
      d = sizeof (VARSTRING) + sizeof (DWORD) + sizeof (DWORD);
    else
      d = sizeof (VARSTRING) + sizeof (HANDLE);
    if ((vstr->dwUsedSize < d) \
     || (vstr->dwStringOffset + vstr->dwStringSize > vstr->dwUsedSize))
    {
      err_exit1:
      n = E_TAPI_GETPORT;
      err_exit2:
      HeapFree (heap, 0L, vstr);
      err_exit3:
      tapi_hangup (dseg);
      return n;
    }
    q = (const DWORD *)((unsigned char *)vstr + vstr->dwStringOffset);
    if (link->service == K_TAPI_LINKSERVICE_VOICE)
      dseg->iochan = (HANDLE)((q[0] & 0x0000FFFFL) | (q[1] << 16));
    else
      dseg->iochan = *((const HANDLE *)q);
    HeapFree (heap, 0L, vstr);
  }
  dseg->callstate = K_TAPI_CALLSTATE_CONNECTED;

  /* Return Baudrate */
  d = link->baudrate / 100;
  if (d > 32767L)
    d = 32767L;
  n = (signed short)d;
  return n;
}

/*----------*/
/*  HANGUP  */
/*----------*/
                                                                    /* [5212] */
signed short tapi_hangup (struct tapi_data *dseg)              /* Hangup Call */
{
  signed short n,m;
  HANDLE heap;
  LONG rc;
  BOOL b;

  /* Check Pointer */
  if (dseg == NULL)
    return E_TAPI_NULLPTR;

  /* Hangup modem */
  n = 0;
  if ((dseg->callstate >= K_TAPI_CALLSTATE_CONNECTED) \
   && (dseg->service < K_TAPI_LINKSERVICE_VOICE))
  {
    /* Close data-channel */
    b = CloseHandle (dseg->iochan);
    if (!b)
      n = E_TAPI_CALLCLOSE;
  }
  if (dseg->callstate > K_TAPI_CALLSTATE_IDLE)
  {
    /* Kill the call */
    rc = lineDrop (dseg->hcall, NULL, 0);
    if (rc >= 0)
    {
      dseg->jobid = rc;
      do
      {
        m = tapi__getmsg (dseg, 1);
      } while ((m >= 0) && (m != K_TAPI_WINMSG_REPLY));
      if (n >= 0)
      {
        if (m < 0)
          n = m;
        else if (dseg->result && (dseg->result != LINEERR_INVALCALLSTATE))
          n = E_TAPI_CALLDROP;
      }
    }
    else if ((rc != LINEERR_INVALCALLSTATE) && (n >= 0))
      n = E_TAPI_CALLDROP;

    /* Wait for idle */
    m = 0;
    while ((dseg->mcstat != LINECALLSTATE_IDLE) && (m >= 0))
      m = tapi__getmsg (dseg, 1);
    if ((m < 0) && (n >= 0))
      n = m;

    /* Free resources */
    heap = GetProcessHeap ();
    HeapFree (heap, 0L, dseg->cstat);
    HeapFree (heap, 0L, dseg->cinfo);
    rc = lineDeallocateCall (dseg->hcall);
    if (rc && (n >= 0))
      n = E_TAPI_CALLDEALLOC;
    dseg->callstate = K_TAPI_CALLSTATE_IDLE;
    dseg->jobid     = 0xFFFFFFFFL;
  }
  return n;
}

/*============================================================================*/
                                                               /* Get Message */
static signed short tapi__getmsg (struct tapi_data *dseg, unsigned char nobrk)
{
  unsigned char t,m;
  signed short n;
  HANDLE heap;
  LONG rc;

  /* Get Message */
  heap = GetProcessHeap ();
  t = K_TAPI_RDTIMEOUT;
  do
  {
    /* Check Break */
    if ((!nobrk) && (dseg->cfg.sigterm != NULL))
      if (*(dseg->cfg.sigterm))
      {
        n = E_TAPI_BREAK;
        break;
      }
    /* Read Message */
    m = 0;
    rc = lineGetMessage (dseg->happ, &(dseg->lmsg), K_TAPI_CHKTIME);
    if (rc == LINEERR_OPERATIONFAILED)
    {
      /* Timeout */
      n = K_TAPI_WINMSG_NONE;      /* 0 */
      t--;
      if (!t)
        m = 1;
    }
    else if (rc)
    {
      /* GetMessage Error */
      n = E_TAPI_GETMSG;
      m = 1;
    }
    else
    {
      /* Parse Message */
      t = 0;
      switch (dseg->lmsg.dwMessageID)
      {
        case LINE_APPNEWCALL:      /* 1 */
          n = K_TAPI_WINMSG_OFFER;
          if ((dseg->lmsg.hDevice  == (DWORD)(dseg->hline)) \
           && (dseg->lmsg.dwParam1 == dseg->cfg.adr))
          {
            if ((dseg->callstate == K_TAPI_CALLSTATE_IDLE) && (!nobrk))
            {
              dseg->hcall = (HCALL)(dseg->lmsg.dwParam2);
              n = tapi__mkcinfos (dseg);
              if (n < 0)
                goto reject;
              dseg->callstate = K_TAPI_CALLSTATE_RINGING;
              n = K_TAPI_WINMSG_OFFER;
              m = 1;
            }
            else
            {
              reject:
              rc = lineDrop ((HCALL)(dseg->lmsg.dwParam2), NULL, 0L);
              if ((rc < 0) && (rc != LINEERR_INVALCALLSTATE) && (n >= 0))
                n = E_TAPI_CALLDROP;
              if (n < 0)
                m = 1;
            }
          }
          break;
        case LINE_REPLY:           /* 2 */
          n = K_TAPI_WINMSG_REPLY;
          if (dseg->lmsg.dwParam1 == dseg->jobid)
          {
            dseg->result = (LONG)(dseg->lmsg.dwParam2);
            m = 1;
          }
          break;
        case LINE_LINEDEVSTATE:    /* 3 */
          n = K_TAPI_WINMSG_LINEDEVSTATE;
          if ((dseg->lmsg.hDevice == (DWORD)(dseg->hline)) \
           || (dseg->lmsg.dwParam1 & LINEDEVSTATE_REINIT))
          {
#ifndef _WIN32_WCE
            dseg->lstat->dwTotalSize = HeapSize (heap, 0L, dseg->lstat);
            rc = lineGetLineDevStatus (dseg->hline, dseg->lstat);
            if (rc)
              n = E_TAPI_LINESTAT;
#endif
            dseg->mlstat = dseg->lmsg.dwParam1;
            m = 1;
          }
          break;
        case LINE_ADDRESSSTATE:    /* 4 */
          n = K_TAPI_WINMSG_ADDRESSSTATE;
          if ((dseg->lmsg.hDevice  == (DWORD)(dseg->hline)) \
           && (dseg->lmsg.dwParam1 == dseg->cfg.adr))
          {
#ifndef _WIN32_WCE
            dseg->astat->dwTotalSize = HeapSize (heap, 0L, dseg->astat);
            rc = lineGetAddressStatus (dseg->hline, dseg->cfg.adr, dseg->astat);
            if (rc)
              n = E_TAPI_ADRSTAT;
#endif
            m = 1;
          }
          break;
        case LINE_CALLINFO:        /* 5 */
          n = K_TAPI_WINMSG_CALLINFO;
          if ((dseg->callstate > K_TAPI_CALLSTATE_IDLE) \
           && (dseg->lmsg.hDevice == (DWORD)(dseg->hcall)))
          {
            dseg->cinfo->dwTotalSize = HeapSize (heap, 0L, dseg->cinfo);
            rc = lineGetCallInfo (dseg->hcall, dseg->cinfo);
            if (rc)
              n = E_TAPI_CALLINFO;
            m = 1;
          }
          break;
        case LINE_CALLSTATE:       /* 6 */
          n = K_TAPI_WINMSG_CALLSTATE;
          if ((dseg->callstate > K_TAPI_CALLSTATE_IDLE) \
           && (dseg->lmsg.hDevice == (DWORD)(dseg->hcall)))
          {
            dseg->cstat->dwTotalSize = HeapSize (heap, 0L, dseg->cstat);
            rc = lineGetCallStatus (dseg->hcall, dseg->cstat);
            if (rc)
              n = E_TAPI_CALLSTAT;
            dseg->mcstat = dseg->lmsg.dwParam1;
            dseg->mcsdet = dseg->lmsg.dwParam2;
            m = 1;
          }
          else if (dseg->lmsg.dwParam1 == LINECALLSTATE_IDLE)
          {
            rc = lineDeallocateCall ((HCALL)(dseg->lmsg.hDevice));
            if (rc)
            {
              n = E_TAPI_CALLDEALLOC;
              m = 1;
            }
          }
          break;
        default:
          n = K_TAPI_WINMSG_UNKNOWN;
      }
    }
  } while (!m);
  return n;
}

/*----*/
                                                         /* Create Call-Infos */
static signed short tapi__mkcinfos (struct tapi_data *dseg)
{
  LINECALLSTATUS *cstat;
  LINECALLINFO *cinfo;
  signed short n;
  HANDLE heap;
  DWORD d,e,f;
  LONG rc;

  /* Call Info */
  heap = GetProcessHeap ();
  cinfo = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (cinfo == NULL)
    return E_TAPI_NOMEM;
  d = HeapSize (heap, 0L, cinfo);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit1;
  cinfo->dwTotalSize  = d;
  cinfo->dwNeededSize = 0L;
  cinfo->dwUsedSize   = 0L;
  rc = lineGetCallInfo (dseg->hcall, cinfo);
  if (rc)
    goto err_exit2;
  e = cinfo->dwNeededSize;
  if (cinfo->dwUsedSize > e)
    e = cinfo->dwUsedSize;
  if (e < sizeof (LINECALLINFO))
    goto err_exit2;
  if (e == d)
    dseg->cinfo = cinfo;
  else
  {
    dseg->cinfo = HeapReAlloc (heap, 0L, cinfo, e);
    if (dseg->cinfo == NULL)
      goto err_exit1;
    cinfo = dseg->cinfo;
    f = HeapSize (heap, 0L, cinfo);
    if (f < e)
    {
      err_exit1:
      n = E_TAPI_NOMEM;
      goto err_exit6;
    }
    cinfo->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetCallInfo (dseg->hcall, cinfo);
      if (rc)
      {
        err_exit2:
        n = E_TAPI_CALLINFO;
        goto err_exit6;
      }
    }
  }

  /* Call Status */
  cstat = HeapAlloc (heap, 0L, K_TAPI_PARBLKLEN);
  if (cstat == NULL)
    goto err_exit1;
  d = HeapSize (heap, 0L, cstat);
  if (d < K_TAPI_PARBLKLEN)
    goto err_exit3;
  cstat->dwTotalSize  = d;
  cstat->dwNeededSize = 0L;
  cstat->dwUsedSize   = 0L;
  rc = lineGetCallStatus (dseg->hcall, cstat);
  if (rc)
    goto err_exit4;
  e = cstat->dwNeededSize;
  if (cstat->dwUsedSize > e)
    e = cstat->dwUsedSize;
  if (e < sizeof (LINECALLSTATUS))
    goto err_exit4;
  if (e == d)
    dseg->cstat = cstat;
  else
  {
    dseg->cstat = HeapReAlloc (heap, 0L, cstat, e);
    if (dseg->cstat == NULL)
      goto err_exit3;
    cstat = dseg->cstat;
    f = HeapSize (heap, 0L, cstat);
    if (f < e)
    {
      err_exit3:
      n = E_TAPI_NOMEM;
      goto err_exit5;
    }
    cstat->dwTotalSize = f;
    if (e > d)
    {
      rc = lineGetCallStatus (dseg->hcall, cstat);
      if (rc)
      {
        err_exit4:
        n = E_TAPI_CALLSTAT;
        err_exit5:
        HeapFree (heap, 0L, cstat);
        err_exit6:
        HeapFree (heap, 0L, cinfo);
        return n;
      }
    }
  }
  dseg->mcstat = LINECALLSTATE_UNKNOWN;
  return 0;
}

/*----*/
                                                          /* Create Link-Info */
static signed short tapi__ci2link (struct tapi_data *dseg, \
                                   struct tapi_link *link)
{
  const unsigned short *pw;
  const unsigned char *pb;
  unsigned char x,*p;
  unsigned short w;
  HANDLE heap;
  DWORD d,e;
  LONG rc;

  /* Update Call-Info */
  heap = GetProcessHeap ();
  dseg->cinfo->dwTotalSize = HeapSize (heap, 0L, dseg->cinfo);
  rc = lineGetCallInfo (dseg->hcall, dseg->cinfo);
  if (rc)
    return E_TAPI_CALLINFO;

  /* Build Link-Info */
  d = 0L;
  e = 0L;
  if (dseg->cinfo->dwOrigin \
   & (LINECALLORIGIN_OUTBOUND | LINECALLORIGIN_CONFERENCE))
  {
    link->dialout = 1;
    if (dseg->cinfo->dwCalledIDFlags & LINECALLPARTYID_ADDRESS)
    {
      d = dseg->cinfo->dwCalledIDSize;
      e = dseg->cinfo->dwCalledIDOffset;
    }
  }
  else
  {
    link->dialout = 0;
    if (dseg->cinfo->dwCallerIDFlags & LINECALLPARTYID_ADDRESS)
    {
      d = dseg->cinfo->dwCallerIDSize;
      e = dseg->cinfo->dwCallerIDOffset;
    }
  }
  pb = (const unsigned char *)(dseg->cinfo) + e;
  p = link->remnumbuf;
  if ((dseg->lcaps->dwStringFormat == STRINGFORMAT_ASCII) \
   || (dseg->lcaps->dwStringFormat == STRINGFORMAT_BINARY))
  {
    if (d >= K_TAPI_DIALNUMBUFLEN)
      d = K_TAPI_DIALNUMBUFLEN - 1;
    for (e=0L; e<d; e++)
    {
      x = *pb++;
      if (!x)
        break;
      if ((x < 0x20) || (x >= 0x80))
        x = 0x3F;  /* ? */
      *p++ = x;
    }
  }
  else if (dseg->lcaps->dwStringFormat == STRINGFORMAT_UNICODE)
  {
    pw = (const unsigned short *)pb;
    if (d < 0L)
      d = 0L;
    d >>= 1;
    if (d >= K_TAPI_DIALNUMBUFLEN)
      d = K_TAPI_DIALNUMBUFLEN - 1;
    for (e=0L; e<d; e++)
    {
      w = *pw++;
      if (!w)
        break;
      if ((w < 0x0020) || (w >= 0x0080))
        w = 0x003F;  /* ? */
      *p++ = (unsigned char)w;
    }
  }
  link->remnumlen = p - link->remnumbuf;
  *p = 0;
  if (dseg->cinfo->dwMediaMode & LINEMEDIAMODE_UNKNOWN)
    link->service = K_TAPI_LINKSERVICE_UNKNOWN;
  else if (dseg->cinfo->dwMediaMode \
    & (LINEMEDIAMODE_DATAMODEM | LINEMEDIAMODE_DIGITALDATA))
    link->service = K_TAPI_LINKSERVICE_DATA;
  else if (dseg->cinfo->dwMediaMode \
    & (LINEMEDIAMODE_G3FAX | LINEMEDIAMODE_G4FAX))
    link->service = K_TAPI_LINKSERVICE_FAX;
  else if (dseg->cinfo->dwMediaMode \
    & (LINEMEDIAMODE_INTERACTIVEVOICE | LINEMEDIAMODE_AUTOMATEDVOICE))
    link->service = K_TAPI_LINKSERVICE_VOICE;
  else
    link->service = K_TAPI_LINKSERVICE_UNKNOWN;
  if (dseg->cinfo->dwBearerMode & LINEBEARERMODE_VOICE)
    link->carrier = K_TAPI_LINKCARRIER_ANA;
  else
    link->carrier = K_TAPI_LINKCARRIER_DIG;
  if (dseg->cinfo->dwRate < 0L)
    link->baudrate = 0L;
  else
    link->baudrate = dseg->cinfo->dwRate;
  return 0;
}

/*----*/
                                                      /* Disconnect-Errorconv */
static signed short tapi__discerr (const struct tapi_data *dseg)
{
  unsigned short n;
  unsigned char x;

  if (dseg->mcstat == LINECALLSTATE_DISCONNECTED)
    for (x=0; x<K_TAPI_DISCMODES; x++)
      if (dseg->mcsdet == tapi__discmode[x])
      {
        n = E_TAPI_CALL_HANGUP - x;
        return n;
      }
  return E_TAPI_CALL_UNKNOWN;
}


/*** EOF ***/
